package main

import (
	"context"
	"google.golang.org/grpc"
	"grpc_etcd/proto"
	"time"
	"fmt"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"go.etcd.io/etcd/clientv3"
	"google.golang.org/grpc/naming"
)

/**
rpc客户端
resolver,watcher实现负载均衡:此方式有轮询效果
即grpc官方提供的resolver,watcher实现仿照简化版本
https://etcd.io/docs/v3.4.0/dev-guide/grpc_naming/
 */
func main(){
  conf := &ResolverConf{
  	key:"student_server",
  	endpoints:[]string{"http://localhost:2379"},
  }
  r,err := NewResolver(conf)
  if err!=nil{
  	panic(err)
  }
  b := grpc.RoundRobin(r)
  conn,err := grpc.DialContext(context.TODO(),
  	conf.key,
  	grpc.WithInsecure(),
  	grpc.WithBalancer(b), //grpc实现负载均衡
  	grpc.WithBlock())
  if err!=nil{
  	panic(err)
  }
  t := time.NewTicker(2 * time.Second)
	for tc := range t.C {
		//请求rpc服务
		client := proto.NewStudentServerClient(conn)
		student,err := client.GetStudent(context.TODO(),&proto.ReqStudent{Id:1})
		if err!=nil{
			panic(err)
			return
		}
		fmt.Println(student,"  ",tc)
	}
}

type Resolver struct {
	cli3 *clientv3.Client
	conf *ResolverConf
}
type ResolverConf struct{
	key string
	endpoints []string
}
type Watcher struct {
	init bool
	resolver *Resolver
}
func NewResolver(conf *ResolverConf)(*Resolver,error){
	cli3,err := clientv3.New(clientv3.Config{
		Endpoints:conf.endpoints,
	})
	if err!=nil{
		return nil,err
	}
	return &Resolver{
		conf:conf,
		cli3:cli3,
	},nil
}

func (r *Resolver)Resolve(target string) (naming.Watcher, error){
	return &Watcher{
		init:     false,
		resolver: r,
	},nil
}

func (wc *Watcher)Next() ([]*naming.Update, error){
	if !wc.init{
		wc.init = true
		gr,err := wc.resolver.cli3.Get(context.TODO(),wc.resolver.conf.key,clientv3.WithPrefix())
		if err!=nil{
			return nil,err
		}
		ups := []*naming.Update{}
		for _, kv := range gr.Kvs {
			ups = append(ups,&naming.Update{Op:naming.Add,Addr:string(kv.Value)})
		}
		return ups,nil
	}
	//监听
	wchan := wc.resolver.cli3.Watch(context.TODO(),wc.resolver.conf.key,clientv3.WithPrefix())
	for e := range wchan {
		for _,ev := range e.Events {
			switch ev.Type {
			case mvccpb.PUT:
				return []*naming.Update{{Op: naming.Add, Addr: string(ev.Kv.Value)}},nil
			case mvccpb.DELETE:
				return []*naming.Update{{Op: naming.Delete, Addr: string(ev.Kv.Value)}},nil
			}
		}
	}
	return nil,nil
}
func (wc *Watcher)Close(){}